package com.gframework.mybatis.util.generator.core.conf;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.gframework.mybatis.util.generator.core.exception.GeneratorException;
import com.gframework.mybatis.util.generator.core.type.JdbcTypeConversion;
import com.gframework.mybatis.util.generator.core.util.StringUtility;


/**
 * 用于获取数据库上下文信息的核心操作类.<br>
 * 首先你需要构建一个配置对象{@link Configuration}来指定你的生成规则，而后通过此对象
 * 来获得本类对象，在通过本类对象取得最终的数据库描述信息对象{@link MetaData}，最后通过数据库描述对象进行代码的生成。
 * 
 * @since 1.0.0
 * @author Ghwolf
 * 
 * @see Configuration
 *
 */
public class Context {
	/**
	 * 配置信息封装对象.
	 */
	private Configuration config;
	
	public Context(Configuration config) {
		this.config = config;
	}
	
	/**
	 * 解析数据表生成对应的描述信息
	 * @return key:模块名称，value:表信息集合
	 */
	public Map<String,List<MetaData>> analysisTables() throws SQLException{
		Connection connection = getConnection();
		try {
			DatabaseMetaData dmd = connection.getMetaData();
			
			Map<String,List<MetaData>> tables = new HashMap<>();
			
			// 便利要进行配置生成的表
			for (Map.Entry<DbFullName, String> entry : this.config.getTableClassifies().entrySet()) {
				String module = entry.getValue();
				DbFullName t = entry.getKey();
				List<MetaData> list = tables.get(module);
				if (list == null) {
					list = new ArrayList<>();
					tables.put(module, list);
				}
				
				String cataLog = t.getCatalog();
				if (dmd.isCatalogAtStart() && cataLog == null) {
					cataLog = t.getSchema();
				}
				
				ResultSet rs = dmd.getTables(cataLog, t.getSchema(), null, null);
				// 对表进行遍历
				while (rs.next()) {
					String tableName = rs.getString("TABLE_NAME");
					if (!tableName.matches(t.getNameReg())) {
						continue ;
					}
					System.out.println("找到一张可以生成的表：" + tableName);
					
					TableMetaData table ;
					String tableType = rs.getString("TABLE_TYPE");
					if ("TABLE".equals(tableType)) {
						table = new TableMetaData();
					} else if ("VIEW".equals(tableType)) {
						table = new ViewMetaData();
					} else if ("SYNONYM".equals(tableType)) {
						table = new SynonymMetaData();
					} else {
						throw new UnknowTableTypeException("未知的表类型：" + tableType);
					}
					list.add(table);
					table.setModuleName(module);
					table.setCatalog(rs.getString("TABLE_CAT"));
					table.setSchema(rs.getString("TABLE_SCHEM"));
					table.setTableName(tableName,this.config.getIgnoreTableNamePrefix(),this.config.getIgnoreTableNameSuffix());
					table.setTableComment(StringUtility.nvl(rs.getString("REMARKS"),"(none-javadoc)"));
					
					ResultSet colrs = dmd.getColumns(table.getCatalog(), table.getSchema(), table.getTableName(),null);
					Set<String> pkNames = getPrimaryKeyList(dmd,table);
					Map<String,List<String>> fkNames = getForeignRelevanceList(dmd,table);
					
					List<String> ignoreCol = getIgnoreColumn(table);
					
					// 遍历表中的列
					while(colrs.next()) {
						String colName = colrs.getString("COLUMN_NAME");
						if (ignoreCol.contains(colName)) {
							continue ;
						}
						
						Column col = new Column();
						col.setCatalog(colrs.getString("TABLE_CAT"));
						col.setSchema(colrs.getString("TABLE_SCHEM"));
						col.setTableName(colrs.getString("TABLE_NAME"));
						col.setColumnName(colName);
						col.setDataTypeName(colrs.getString("TYPE_NAME"));
						col.setColumnSize(colrs.getInt("COLUMN_SIZE"));
						col.setDecimalDigits(colrs.getInt("DECIMAL_DIGITS"));
						col.setNullable(colrs.getInt("NULLABLE") == 1);
						col.setColumnComment(colrs.getString("REMARKS"));
						col.setAutoincrement("YES".equals(colrs.getString("IS_AUTOINCREMENT")));
						// 其他信息
						col.setPrimaryKey(pkNames.contains(col.getColumnName()));
						col.setJavaType(JdbcTypeConversion.convert(colrs.getInt("DATA_TYPE"), col.getColumnSize(),
								col.getDecimalDigits(),this.config.isOnlyCommonType()));
						List<String> fkNs = fkNames.get(col.getColumnName());
						if (fkNs != null) {
							col.getForeignRelevance().addAll(fkNs);
						}
						table.addColumn(col);
					}
					
				}
				
			}
			return tables;
		} finally {
			connection.close();
		}
	}
	
	/**
	 * 取得在此表中所有忽略的列名称集合.
	 * @param table 当前操作的表
	 * @return 返回忽略的列名称集合，如果没有集合长度为0
	 */
	private List<String> getIgnoreColumn(MetaData table) {
		List<String> list = new ArrayList<>();
		for (Map.Entry<DbFullName, List<String>> entry : this.config.getIgnoreColumns().entrySet()) {
			DbFullName tf = entry.getKey();
			boolean b1 = StringUtility.stringIsNullOrEquals(tf.getCatalog(),table.getCatalog());
			boolean b2 = StringUtility.stringIsNullOrEquals(tf.getSchema(),table.getSchema());
			if (b1 && b2 && table.getTableName().matches(tf.getNameReg())) {
				list.addAll(entry.getValue());
			}
		}
		return list ;
	}
	

	/**
	 * 取得一个表的所有被关联外键信息
	 * @param dmd 数据库MetaData对象
	 * @param table 表元对象
	 * @return 返回被关联外键信息，key:列名称，value:被关联外键列全名，如果没有，集合长度为0
	 */
	private Map<String,List<String>> getForeignRelevanceList(DatabaseMetaData dmd,MetaData table) throws SQLException{
		ResultSet fkrs = dmd.getExportedKeys(table.getCatalog(), table.getSchema(), table.getTableName());
		Map<String,List<String>> fkNames = new HashMap<>(8);
		while(fkrs.next()) {
			StringBuilder sb = new StringBuilder();
			String s ;
			if ( (s = fkrs.getString("FKTABLE_CAT")) != null ) {
				sb.append(s).append('.');
			}
			if ( (s = fkrs.getString("FKTABLE_SCHEM")) != null ) {
				sb.append(s).append('.');
			}
			sb.append(fkrs.getString("FKTABLE_NAME")).append('.');
			sb.append(fkrs.getString("FKCOLUMN_NAME"));
			
			String key = fkrs.getString("PKCOLUMN_NAME");
			List<String> list = fkNames.get(key);
			if (list == null) {
				list = new ArrayList<>();
				fkNames.put(key,list);
			}
			list.add(sb.toString());
		}
		return fkNames ;
	}
	/**
	 * 取得一个表的所有主键信息
	 * @param dmd 数据库MetaData对象
	 * @param table 表元对象
	 * @return 返回主键名称Set集合，如果没有，集合长度为0
	 */
	private Set<String> getPrimaryKeyList(DatabaseMetaData dmd,MetaData table) throws SQLException{
		ResultSet pkrs = dmd.getPrimaryKeys(table.getCatalog(), table.getSchema(), table.getTableName());
		Set<String> pkNames = new HashSet<>(1);
		
		while(pkrs.next()) {
			if (pkNames.size() > 1) {
				throw new GeneratorException("生成失败，针对复合主键的表暂未作处理！表名称：" + table.getTableName());
			}
			pkNames.add(pkrs.getString("COLUMN_NAME"));
		}
		return pkNames ;
	}
	
	/**
	 * 取得数据库连接对象
	 */
	private Connection getConnection() {
		try {
			return DriverManager.getConnection(this.config.getDburl(),this.config.getJdbcParam());
		} catch(Exception e) {
			throw new ConfigurationException("数据库连接失败，可能是网络问题或连接信息配置有误！",e);
		}
	}
}
